package com.haredot.service.impl;

import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayOpenAuthTokenAppModel;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayOpenAuthTokenAppRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayOpenAuthTokenAppResponse;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haredot.constraint.RabbitConst;
import com.haredot.dto.AlipayDTO;
import com.haredot.dto.AuthorizeUserDTO;
import com.haredot.entity.Order;
import com.haredot.entity.SvipConfig;
import com.haredot.entity.UserScore;
import com.haredot.entity.enums.OrderStatus;
import com.haredot.mapper.*;
import com.haredot.properties.AlipayProperties;
import com.haredot.properties.JwtProperties;
import com.haredot.service.AlipayService;
import com.haredot.utils.JwtUtils;
import com.haredot.utils.RSA;
import com.haredot.vo.R;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

// afoscg3707@sandbox.com
@Service
@Transactional(rollbackFor = Exception.class)
@EnableConfigurationProperties({AlipayProperties.class, JwtProperties.class})
public class AlipayServiceImpl extends ServiceImpl<OrderMapper, Order> implements AlipayService {

    @Resource
    private SvipConfigMapper svipConfigMapper;

    @Resource
    private AlipayClient alipayClient;

    @Resource
    private AlipayProperties alipayProperties;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private UserScoreMapper userScoreMapper;

    @Resource
    private AuthorizeUserMapper authorizeUserMapper ;

    @Resource
    private JwtProperties jwtProperties ;

    @Resource
    private RSA rsa ;

    @Override
    public R<?> generatorPayPage(Long id, Long userId) throws Exception {

        // 根据 套餐ID ,查询 套餐信息
        SvipConfig svipConfig = svipConfigMapper.selectById(id);
        // 获取 要支付的 金额
        String price = svipConfig.getPrice();
        String subject = "会员充值-" + svipConfig.getName();

        String orderNum = generatorOrderNum();  // 生成一个订单编号
        // 判断 redis 是否存在，做幂等性操作
        if (stringRedisTemplate.opsForValue().setIfAbsent("order:generator:page::" + userId + "::" + id, orderNum, Duration.ofMinutes(1))) {
            // 将订单信息 写入到 订单表中
            Order order = Order.builder()
                    .orderNum(orderNum)
                    .money(price)
                    .payType(subject)
                    .vipConfigId(id)
                    .userId(userId)
                    .state(OrderStatus.WAIT_BUYER_PAY)  // 未支付
                    .build();
            // 写入订单表中
            this.getBaseMapper().insert(order);

        } else {
            orderNum = stringRedisTemplate.opsForValue().get("order:generator:page::" + userId + "::" + id);
        }

        // 将订单信息 写入到 rabbitmq 中 ....
        rabbitTemplate.send(RabbitConst.ORDER_EXCHANGE, RabbitConst.ORDER_ROUTING_KEY, new Message(orderNum.getBytes(StandardCharsets.UTF_8)));

        // 调用支付宝 alipay.trade.page.pay（统一收单下单并支付页面接口） 接口，生成支付页面
        return generatorPayPage(orderNum, price, subject);
    }

    private R<?> generatorPayPage(String orderNum, String price, String subject) throws Exception {
        // 创建一个 bizModel 对象，用来设置 请求业务参数
        AlipayTradePagePayModel bizModel = new AlipayTradePagePayModel();
        bizModel.setOutTradeNo(orderNum); // 设置商户订单编号
        bizModel.setTotalAmount(price); // 设置支付金额
        bizModel.setSubject(subject); //订单标题
        bizModel.setProductCode("FAST_INSTANT_TRADE_PAY");

        // 创建一个 请求对象
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setBizModel(bizModel);
        // 设置 回调地址
        request.setReturnUrl(alipayProperties.getReturnUrl());
        request.setNotifyUrl(alipayProperties.getNotifyUrl());

        // 执行请求， 并生成支付页面
        AlipayTradePagePayResponse alipayTradePagePayResponse = alipayClient.pageExecute(request);
        // 接口调用成功
        if (alipayTradePagePayResponse.isSuccess()) {
            // 获取 生成的支付页面
            String body = alipayTradePagePayResponse.getBody();
            // 返回结果
            return R.ok().data(body);
        }

        return R.fail().build();
    }

    @Override
    public R<?> payOrder(String orderNum) throws Exception {
        // 根据订单编号，查询订单信息
        Order order = this.getBaseMapper().selectOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNum, orderNum));
        if (order == null) return R.fail(R.StatusCode.BAD, "订单编号不存在").build();
        // 判断订单状态是否是待支付
        if (order.getState() != OrderStatus.WAIT_BUYER_PAY) return R.fail(R.StatusCode.BAD, "订单状态为" + order.getState()).build();
        // 生成订单支付页面
        return generatorPayPage(orderNum, order.getMoney(), order.getPayType());
    }

    @Override
    public R<?> checkParam(Map<String, String> params) throws Exception {
        // 对传入的参数进行 验签
        boolean check = AlipaySignature.verifyV1(params,
                alipayProperties.getAlipayPublicKey(),
                alipayProperties.getCharset(),
                alipayProperties.getSignType());

        // 验证签名失败、直接返回失败
        if (!check) return R.fail(32000, "验证签名失败").build();

        // 如果 验签成功、（1. 修改订单状态为 支付成功、 2. 开通会员）
        String out_trade_no = params.get("out_trade_no");
        // 根据 订单编号、查询 该笔订单
        Order order = this.getBaseMapper().selectOne(new QueryWrapper<Order>().eq("order_num", out_trade_no));

        // 此时 该订单 有可能 是支付成功的 ，也有可能是 待支付的。
        if (order.getState() == OrderStatus.TRADE_SUCCESS || order.getState() == OrderStatus.TRADE_FINISHED) {
            return R.ok().build();
        }
        // 如果 该笔订单是 待支付状态 ， 说明 （notify_url 没有执行 或者 正在执行中， 或者 出现了 异常， 要么没提供 notify_url）
        return checkPayState(out_trade_no, order) ;
    }

    @Override
    public R<?> checkOrder(String orderNum) throws Exception {
        // 根据 订单编号、查询 该笔订单
        Order order = this.getBaseMapper().selectOne(new QueryWrapper<Order>().eq("order_num", orderNum));
        return checkPayState(orderNum, order);
    }

    private R<?> checkPayState(String out_trade_no, Order order) throws Exception {
        // 查询支付接口，获取支付状态 alipay.trade.query
        AlipayTradeQueryModel bizModel = new AlipayTradeQueryModel();
        bizModel.setOutTradeNo(out_trade_no);
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        request.setBizModel(bizModel);
        // 执行请求, 并获取响应结果
        AlipayTradeQueryResponse response = alipayClient.execute(request);

        if (response.isSuccess()) {
            // 获取 该比订单的 状态
            String tradeStatus = response.getTradeStatus();
            if (tradeStatus.equals(OrderStatus.TRADE_SUCCESS.name()) || tradeStatus.equals(OrderStatus.TRADE_FINISHED.name())) {
                // 修改订单表的状态 ， 并开通 会员 (要保障 修改状态，开通会员 在 同步请求 和 异步请求中 只会生效一次)

                // 判断 redis 中是否存在 某个特殊的键 ( modify:state:vip:订单编号) , 如果 键不存在，则 将 键 写入到 redis 中，
                Boolean aBoolean = stringRedisTemplate.opsForValue()
                        .setIfAbsent("modify:state:vip::" + out_trade_no, out_trade_no, Duration.ofDays(1));

                if (aBoolean) {
                    // 开通会员 和 修改订单状态
                    this.getBaseMapper().update(null, new UpdateWrapper<Order>()
                            .set("state", OrderStatus.valueOf(tradeStatus))
                            .set("pay_time", LocalDateTime.now())
                            .eq("id", order.getId())
                    );

                    // 开通会员、查询 该笔订单对应的会员套餐
                    SvipConfig svipConfig = svipConfigMapper.selectById(order.getVipConfigId());

                    // 赠送积分
                    UserScore userScore = UserScore.builder()
                            .action("开通" + svipConfig.getName())
                            .userId(order.getUserId())
                            .score(svipConfig.getScore())
                            .build();
                    // 赠送积分
                    userScoreMapper.insert(userScore);

                    // 设置 会员 （redis 存入一个 svip+ 用户ID）
                    // 如果 键存储，说明该用户 本身已经是 SVIP 用户，再次开通，需要增加过期时间
                    if (stringRedisTemplate.hasKey("svip:" + order.getUserId())) {
                        // 判断 原会员是否 是永久会员
                        Long expire = stringRedisTemplate.getExpire("svip:" + order.getUserId(), TimeUnit.MILLISECONDS);
                        if (expire != -1 && svipConfig.getExp() == -1) { // 如果 原会员不是永久会员，但本次充值为永久会员
                            stringRedisTemplate.persist("svip:" + order.getUserId());
                        } else if (expire != -1) {  // 两次充值均不会永久会员
                            Integer exp = svipConfig.getExp(); // 天
                            Duration duration = Duration.ofDays(exp).plusMillis(expire);
                            stringRedisTemplate.expire("svip:" + order.getUserId(), duration);
                        }
                    } else {
                        // 会员过期 或者 第一次开通会员
                        stringRedisTemplate.opsForValue().set("svip:" + order.getUserId(), order.getOrderNum());
                        if (svipConfig.getExp() > 0) {
                            stringRedisTemplate.expire("svip:" + order.getUserId(), Duration.ofDays(svipConfig.getExp()));
                        }
                    }
                }

                return R.ok().build();
            }
        }

        return R.fail(32000, "支付失败").build();
    }


    /**
     * 生成订单编号
     *
     * @return
     */
    private String generatorOrderNum() {
        // 获取 距离 1970年的 秒数
        long epochSecond = Instant.now().getEpochSecond();

        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        sb.append(epochSecond);
        // 拼接 7位随机数
        for (int i = 0; i < 7; i++) {
            int num = random.nextInt(10);
            sb.append(num);
        }
        return sb.toString();
    }


    @Override
    public R<?> authCodeToToken(AlipayDTO alipayDTO) throws Exception{
        // 1. 调用支付宝接口、完成 根据授权码 换取 令牌的操作 alipay.open.auth.token.app
        AlipayOpenAuthTokenAppModel bizModel = new AlipayOpenAuthTokenAppModel();
        bizModel.setGrantType("authorization_code");
        bizModel.setCode(alipayDTO.getApp_auth_code());
        // 2. 构建该接口的请求对象
        AlipayOpenAuthTokenAppRequest request = new AlipayOpenAuthTokenAppRequest() ;
        request.setBizModel(bizModel);

        // 3. 调用接口，获取令牌
        AlipayOpenAuthTokenAppResponse response = alipayClient.execute(request);

        // 4. 获取接口响应的结果
        if (!response.isSuccess()) {
            return R.fail(response.getSubMsg()).build();
        }
        // 5. 获取 对应的 支付宝 用户 ID , 和令牌 getAppAuthToken
        String alipayUserID = response.getUserId();
        // 6. 根据 支付宝 用户ID , 查询 该用户 是否 和 项目的用户 进行绑定
        AuthorizeUserDTO authorizeUser = authorizeUserMapper.findAuthorizeUserByAlipayUserId(alipayUserID);
        // 说明该账号已经和某一个用户进行了绑定
        if (authorizeUser != null) {
            // 如果验证码 正确， 则代表 登录成功 ，校验用户信息
            UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
            // 校验用户信息
            try {
                userDetailsChecker.check(authorizeUser.getUser());
                // 没有多大问题，准备生成 令牌信息
                Map<String, Object> ret = JwtUtils.generatorToken(stringRedisTemplate, authorizeUser.getUser(), jwtProperties, true, true,  false);
                return R.ok().data(ret);

            } catch (AuthenticationException e) {
                return R.fail(e.getMessage()).build();
            }
        }

        String message = "uid=" + alipayUserID + "&method=oauth2&type=alipay" ;

        // 对消息进行签名
        String sign = URLEncoder.encode(rsa.sign(message), StandardCharsets.UTF_8);

        // 如果没有查询到授权的用户，则 引导用户 绑定系统账号
        return R.fail(32000, "暂无绑定任何账号").data(Map.of("uid", alipayUserID, "sign", sign, "type", "alipay"));
    }
}
